from deslab import *
import string
from subprocess import call, check_output, CalledProcessError, Popen, PIPE
from random import randint, sample
from FAdo.rndfa import ICDFArnd 
import string
import itertools
SIGMA = dict(zip(range(0,27), string.ascii_lowercase,))
from sys import  maxint

"""THE FINAL SOLUTION IS TO USE GENRATORS"""

""" inteface for random automata """



def random_fsa(n,k):
    H = ICDFArnd(n,k,complete=False)
    H1 = next(H)
    tag, X, S1, T1, X0, Xm = H1.dump()
    S= [SIGMA[e] for e in S1]
    T= [(t[0],SIGMA[t[1]] ,t[2]) for t in T1]    
    G = fsa(X, S, T, X0, Xm)   
    return G


def random2fsa(ICDFA):
    H1 = next(ICDFA)
    tag, X, S1, T1, X0, Xm = H1.dump()
    S = [SIGMA[e] for e in S1]
    T= [(t[0],SIGMA[t[1]] ,t[2]) for t in T1]    
    G = fsa(X, S, T, X0, Xm)   
    return G


"""parser for REGAL automata"""


def rnd_incomplete_fsa(num_states, num_events, num_aut):   
    command = './desgenerator '+str(num_states)+' '+str(num_events)+' '+str(num_aut)
    try:
        list_code = check_output(command, shell=True)  
    except CalledProcessError:
        raise deslabError, "There is a trouble with the compiled code of automaton"           
    print "loaded to memory"
    list_code = list_code.split("@\n")[0:-1]
    X = range(num_states)
    X0 = [0]
    S = [SIGMA[i] for i in range(num_events)]
    for aut_code in list_code:
        print "parsing code"
        T=[]
        Xm=[]
        lines = aut_code.split('\n')[8:-4]
        data = [s.split()[1:] for s in lines]
        for x, row in enumerate(data):
            if row[-1]=='1':
                Xm.append(x)
            T += [[x, SIGMA[i], int(xn)] for i, xn in enumerate(row[0:-2]) if xn <> '?']
        R = fsa(X,S,T,X0,Xm,name='RA with'+str(num_states)+' states and '+str(num_events)+' events' )
        yield R


def rnd_tree_fsa(num_states, num_events, num_aut):   
    command = './treegenerator '+str(num_states)+' '+str(num_events)+' '+str(num_aut)
    try:
        retcode = check_output(command, shell=True)  
    except CalledProcessError:
        raise deslabError, "There is a trouble with the compiled code of automaton"

    list_code = retcode.split("@\n")[0:-1]
    X = range(num_states)
    X0 = [0]
    S = [SIGMA[i] for i in range(num_events)]

    for aut_code in list_code:
        T=[]
        Xm=[]
        lines = aut_code.split('\n')[8:-4]  
        data = [s.split()[1:] for s in lines]
        for x, row in enumerate(data):
            if row[-1]=='1':
                Xm.append(x)
            T += [[x, SIGMA[i], int(xn)] for i, xn in enumerate(row[0:-2]) if xn <> '?']
        NL = fsa(X,S,T,X0,Xm,name='RA with'+str(num_states)+' states and '+str(num_events)+' events' )
        for x in NL.X:
            if NL.Gamma(x) == set():
                e =sample(S,1)[0]
                NL = NL.addselfloop(x,e)
        yield NL


def rnd_complete_fsa(num_states, num_events, num_aut):   
    command = './compgenerator '+str(num_states)+' '+str(num_events)+' '+str(num_aut)
    try:
        retcode = check_output(command, shell=True)  
    except CalledProcessError:
        raise deslabError, "There is a trouble with the compiled code of automaton"
           
    list_code = retcode.split("@\n")[0:-1]
    R = []
    X = range(num_states)
    X0 = [0]
    S = [SIGMA[i] for i in range(num_events)]

    for aut_code in list_code:
        T=[]
        Xm=[]
        lines = aut_code.split('\n')[8:-4]
        data = [s.split()[1:] for s in lines]
        for x, row in enumerate(data):
            if row[-1]=='1':
                Xm.append(x)
            T += [[x, SIGMA[i], int(xn)] for i, xn in enumerate(row[0:-2]) if xn <> '?']
        R = fsa(X,S,T,X0,Xm,name='RA with'+str(num_states)+' states and '+str(num_events)+' events' )
        yield R



#def rnd_varstructure_fsa_set(num_states, num_events, num_aut, spar = 0):
#    if spar ==0 :
#        return random_tree_fsa_set(num_states, num_events, num_aut)
#    elif spar == 1:
#        return random_complete_fsa_set(num_states, num_events, num_aut)
#    elif 0 < spar < 1:  
#               
#        A = iter(random_complete_fsa_set(num_states, num_events, num_aut))    
#        H = []
#        for G in A:
#            T_lex = set(lexgraph_transitions(G))
#            T_G = set(G.transitions())
#            T_comp = T_G - T_lex
#            N = int(len(T_comp)*(1-spar))
#            T_del = sample(T_comp, N)
#            for t in T_del:
#                G = G.deletetransition(t)    
#            for x in G:
#                if G.Gamma(x) == set():
#                    e =sample(G.Sigma,1)[0]
#                    G = G.addselfloop(x,e)  
#            H.append(G)
#    return H 


    
def lexgraph_transitions(G):    
    """Return a list of states whose order is given by the 
    lexicografical depth first search from initial state """
    x0 = G._X0()
    events_x0 = sorted(list(G.Gamma(x0)))
    stack = []
    visited = [x0]   
    node = [epsilon]             
    stack = [(x0, iter(events_x0))]     
    T = []         
    while stack:
        x, event_iter = stack[-1]   
        try:
            e = next(event_iter)
            x_n = G.delta(x, e)        
            if x_n  not in visited:                       
                visited.append(x_n)
                events_new = sorted(list(G.Gamma(x_n)))                
                stack.append((x_n,iter(events_new)))    
                T.append((x, e, x_n))         
        except StopIteration:
            stack.pop()                 
    return T


def cstr2fsa(S, n, k):
    X = xrange(0,n)
    Sigma = [SIGMA[i] for i in xrange(0,k)]
    # trying to locate the transitions
    T=[]
    X0 = [0]
    Xm = []
    for xi in xrange(0, n):
        for ei in range(0, k):
            T.append((xi, SIGMA[ei], S[xi*k+ei]))
    G = fsa(X,Sigma,T,X0,Xm)
    return G


def istr2fsa(S, ni, ki):    
    X = range(1,ni+1)   
    Sigma = [SIGMA[i] for i in xrange(0,ki)]
    # trying to locate the transitions
    T=[]
    X0 = [1]
    Xm = []
    for xi in xrange(1, ni+1):
        for ei in range(0, ki):
            if S[xi*ki+ei] <> 0:
                T.append((xi, SIGMA[ei], S[xi*ki+ei]))
    G = fsa(X,Sigma,T,X0,Xm)
    return G


"""We try to generate the partitions"""



def exh_complete_fsa(n, k):
    """This instruction returns a generator that generates all the transition
    structures of with a fixed number of states (n_states) and a fixed number of
    events (n_events)  """
    
    global S, F, FS, I
      
        
    def reset():    
        R = [0 for a in xrange(k*n)]
        for i,f in enumerate(F):
            R[f] = i+1
        return R   
    
           
    def nextflags(i):
        global F
        if i==1:
            F[i-1] = F[i-1] - 1
        else:
            if F[i-1]-1==F[i-2]:
                F[i-1] = k*i-1
                nextflags(i-1)
            else:
                F[i-1] = F[i-1]-1                
               
    
    def find_fj(i):
        global F
        fmin = maxint 
        for j, f in enumerate(F):
            fa = i-f
            if  fa > 0:
                if fa < fmin:
                    fmin = fa
                    f_j = j
        return f_j    
    
       
    def nexticdfa(a,b):
        global F
        global S
        i = a*k + b
        if a < n-1:
            while i in F:
                b = b-1
                i = i-1
        fj = F[find_fj(i)]
        if S[i] == S[fj]:
            S[i] = 0
            if b==0:
                nexticdfa(a-1,k-1)
            else:
                nexticdfa(a,b-1)
        else:
            S[i] = S[i]+1
    
    def calc_IS():
        global F
        IS = []
        fa=0
        for i,f in enumerate(F):
            t = [i for a in xrange(fa,f)]
            IS += t
            fa = f
        IS += [n-1 for a in xrange(F[-1], k*n)]
        return IS
        
    def calc_FS():        
        FS = range(1,n)
        FS += [n-1 for i in xrange(0,(k-1)*n+1)]
        return FS
        
    
    comp_inst = compCount()
    count = comp_inst.counter  
        
    if count == 0:
        F = [k*i-1 for i in range(1, n)]
        S = reset()   
        FS = calc_FS()    
        I = calc_IS()           
        return S
    elif S == FS:
        return None
    elif S == I:
        nextflags(n-1)
        S = reset()        
        return S
    else:
        nexticdfa(n-1,k-1)
        I = calc_IS()
        return S



def exh_incomplete_fsa(ni, k):
    """This instruction returns a generator that generates all the transition
    structures of with a fixed number of states (n_states) and a fixed number of
    events (n_events)  """
    
    global S, F, FS, I, n
      
        
    def reset():    
        R = [0 for a in xrange(k*n)]
        for i,f in enumerate(F):
            R[f] = i+1
        return R   
    
           
    def nextflags(i):
        global F
        if i==1:
            F[i-1] = F[i-1] - 1
        else:
            if F[i-1]-1==F[i-2]:
                F[i-1] = k*i-1
                nextflags(i-1)
            else:
                F[i-1] = F[i-1]-1                
               
    
    def find_fj(i):
        global F
        fmin = maxint 
        for j, f in enumerate(F):
            fa = i-f
            if  fa > 0:
                if fa < fmin:
                    fmin = fa
                    f_j = j
        return f_j    
    
       
    def nexticdfa(a,b):
        global F
        global S
        i = a*k + b
        if a < n-1:
            while i in F:
                b = b-1
                i = i-1
        fj = F[find_fj(i)]
        if S[i] == S[fj]:
            S[i] = 0
            if b==0:
                nexticdfa(a-1,k-1)
            else:
                nexticdfa(a,b-1)
        else:
            S[i] = S[i]+1
    
    def calc_IS():
        global F
        IS = []
        fa=0
        for i,f in enumerate(F):
            t = [i for a in xrange(fa,f)]
            IS += t
            fa = f
        IS += [n-1 for a in xrange(F[-1], k*n)]
        return IS
        
    def calc_FS():        
        FS = [0 for a in xrange(0, k-1)]
        FS += range(1,n)
        FS += [n-1 for i in xrange(0,(k-1)*(n-1)+1)]
        return FS
        
    comp_inst = compCount()
    count = comp_inst.counter  
        
    if count == 0:
        n =  ni+1
        F = [k*i-1 for i in range(1, n)]
        S = reset()   
        FS = calc_FS()   
        I = calc_IS()  
                        
        return S
    elif S == FS:
        return None
    elif S == I:
        nextflags(n-1)
        S = reset()        
        return S
    else:
        nexticdfa(n-1,k-1)
        I = calc_IS()
        return S

         